package com.beeweeb.services.tps;

import com.beeweeb.services.tps.text.DefaultSimpleDateFormat;
import com.beeweeb.services.tps.concurrent.RunnableMethod;
import com.beeweeb.services.tps.concurrent.UnboundedThreadPoolExecutor;
import com.beeweeb.services.tps.mbean.ThreadPoolSchedulerMBean;
import java.lang.reflect.Method;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JobInvoker implements Job {

    Logger logger = LoggerFactory.getLogger("THREADPOOL-SCHEDULER");
    Logger mailLogger = LoggerFactory.getLogger("THREADPOOL-SCHEDULER-MAIL");
    DefaultSimpleDateFormat sdf = new DefaultSimpleDateFormat();

    public void execute(JobExecutionContext jec) throws JobExecutionException {
        UnboundedThreadPoolExecutor threadPool;
        Object jobExecutorClass;
        int jobPoolAcceptedCount;
        boolean jobPoolExclusiveExecution;

        logger.info("Scheduler programmed at {} has been triggered at {}.", sdf.format(jec.getScheduledFireTime()), sdf.format(jec.getScheduledFireTime()));
        logger.info("Thread name: {}", Thread.currentThread().getName());

        threadPool = (UnboundedThreadPoolExecutor) jec.getMergedJobDataMap().get("ThreadPool");
        jobExecutorClass = jec.getMergedJobDataMap().get("JobPoolExecutorClass");
        jobPoolAcceptedCount = (Integer) jec.getMergedJobDataMap().get("jobPoolAcceptedCount");
        jobPoolExclusiveExecution = (Boolean) jec.getMergedJobDataMap().get("jobPoolExclusiveExecution");

        ThreadPoolSchedulerMBean mbean = (ThreadPoolSchedulerMBean) jec.getMergedJobDataMap().get("ThreadPoolSchedulerMBean");

        if (mbean.isActiveClusterMember() || !mbean.isClusterConnected()) {

            if (jobExecutorClass != null) {

                // Qui chiamo lo scheduler esterno
                JobPool p = new JobPool(jobPoolAcceptedCount);

                invokeJobPoolExecutor((Class) jobExecutorClass, p);

                if (!(jobPoolExclusiveExecution && (threadPool.getRunningThreadsCount() != 0))) {
                    invokeJobs(threadPool, p);

                    //se deve notificare che tutti i jobs sono finiti
                    if (mbean.isNotifyJobsCompleted() && threadPool.getThreadStartedCount() > 0) {
                        //attendi che tutti i jobs siano completati
                        while (true) {
                            if (threadPool.getEnqueuedThreadsCount() == 0 && threadPool.getRunningThreadsCount() == 0) {
                                break;
                            }
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex) {
                                logger.error("InterruptedException: " + ex);
                            }
                        }
                        //notifica il completamento dei jobs
                        invokeJobsCompletedExecutor((Class) jobExecutorClass);
                    }
                } else {
                    logger.info("A new job pool is ready. Skip execution because exclusive execution policy is set.");

                    // INFORMAZIONI DI SERVIZIO VIA EMAIL ///////
                    String message;
                    message = "*** WARNING *** \n\nA new job pool is ready to be executed but it cannot run because another pool has been executing.\n";

                    message += "List of jobs skipped:";

                    for (int i = 0; i < p.getClasses().size(); i++) {
                        message += "Job#" + i + ": " + p.getClasses().get(i) + " with parameter " + p.getParameters().get(i);
                    }
                    mailLogger.warn(message);
                    ///////////////////////////////////////
                }
            } else {
                logger.error("Cannot start scheduler. Job pool executor classclass not found.");
                mailLogger.warn("*** WARNING *** \n\nCannot start scheduler. Job pool executor classclass not found.");
            }
        } else {
            logger.info("The scheduler has been called but it won't start. Scheduler cluster node is in passive state (backup node).");
            mailLogger.warn("*** WARNING *** \n\nThe scheduler has been called but it won't start. Scheduler cluster node is in passive state (backup node).");
        }
    }

    private void invokeJobs(UnboundedThreadPoolExecutor threadPool, JobPool pool) {
        Method executor;
        Runnable runnable;

        for (int i = 0; i < pool.classes.size(); i++) {
            Class executorClass = pool.getClasses().get(i);
            Object parameter = pool.getParameters().get(i);

            if (parameter != null) {
                executor = findMethodWithAnnotation(executorClass, JobExecutor.class, parameter.getClass());
            } else {
                executor = findMethodWithAnnotation(executorClass, JobExecutor.class, null);
            }

            try {
                if (executor != null) {
                    if (parameter != null) {
                        runnable = new RunnableMethod(executorClass, executor, parameter);
                    } else {
                        runnable = new RunnableMethod(executorClass, executor);
                    }

                    threadPool.execute(runnable);
                } else {
                    logger.warn("Job executor method not found into class '{}'.", executorClass.getSimpleName());
                }
            } catch (Exception ex) {
                logger.error("Job executor invoke error found. ", ex);
            }
        }
    }

    private void invokeJobPoolExecutor(Class c, JobPool p) {
        Method executor = findMethodWithAnnotation(c, JobPoolExecutor.class, JobPool.class);

        try {
            if (executor != null) {
                Object instance = c.newInstance();
                executor.invoke(instance, p);
            } else {
                logger.warn("Job Pool executor method not found into class '{}'.", c.getSimpleName());
            }
        } catch (Exception ex) {
            logger.error("Job Pool executor invoke error found. ", ex);
        }

    }
    
    private void invokeJobsCompletedExecutor(Class c) {
        Method executor = findMethodWithAnnotation(c, JobsCompletedExecutor.class, null);

        try {
            if (executor != null) {
                Object instance = c.newInstance();
                executor.invoke(instance);
            } else {
                logger.warn("Jobs Completed executor method not found into class '{}'.", c.getSimpleName());
            }
        } catch (Exception ex) {
            logger.error("Jobs Completed executor invoke error found. ", ex);
        }

    }

    private Method findMethodWithAnnotation(Class targetClass, Class annotationClass, Class parameterClass) {
        Method result = null;

        for (Method m : targetClass.getMethods()) {

            if (m.getAnnotation(annotationClass) != null) {

                if (parameterClass != null) {

                    if ((m.getParameterTypes().length == 1)
                            && (m.getParameterTypes()[0].isAssignableFrom(parameterClass))) {
                        result = m;
                        break;
                    }
                } else {
                    // Method without parameter
                    if (m.getParameterTypes().length == 0) {
                        result = m;
                        break;
                    }
                }
            }
        }

        return result;
    }
}
